/*
 * 
 */
package co.edu.udistrital.erpgrid.interceptor;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.SQLException;

import org.openswing.swing.message.receive.java.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;

import co.edu.udistrital.erpgrid.bd.modelo.pojo.Estado;
import co.edu.udistrital.erpgrid.bd.modelo.pojo.GridNodo;
import co.edu.udistrital.erpgrid.exceptions.CrudException;
import co.edu.udistrital.erpgrid.exceptions.FinderException;
import co.edu.udistrital.erpgrid.jaio.commons.JaioObjectVO;
import co.edu.udistrital.erpgrid.jaio.commons.SimpleDataSource;
import co.edu.udistrital.erpgrid.trabajos.servicio.api.ITrabajosServicio;
import co.edu.udistrital.erpgrid.util.XmlUtil;
import co.edu.udistrital.erpgrid.utilidades.consola.Consola;
import co.edu.udistrital.erpgrid.utilidades.ftp.FtpUtil;

// TODO: Auto-generated Javadoc
/**
 * Fabrica para crear instancias de objetos InterceptorBean.
 */
public class InterceptorBeanFactory {

	/** La instance. del interceptor */
	private static InterceptorBeanFactory instance;

	/**
	 * Logger.
	 */
	private static final Logger LOG = LoggerFactory
			.getLogger(InterceptorBeanFactory.class);

	/**
	 * Gets the single instance of InterceptorBeanFactory.
	 *
	 * @return single instance of InterceptorBeanFactory
	 */
	public static InterceptorBeanFactory getInstance() {
		if (instance == null) {
			instance = new InterceptorBeanFactory();
		}
		return instance;
	}

	/**
	 * The trabajos servicio impl.
	 * @uml.property  name="trabajosServicioImpl"
	 * @uml.associationEnd  readOnly="true"
	 */
	@Autowired
	private transient ITrabajosServicio trabajosServicioImpl;

	/**
	 * Instantiates a new interceptor bean factory.
	 */
	public InterceptorBeanFactory() {
		try {
			SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
		} catch (Exception e) {
			LOG.error("Creacion AOP" + e.getMessage(),e);
		}
		
	}

	/**
	 * Metodo para interceptar la ejecucion de una funcion del servidor de JAIO.
	 *
	 * @param objectVO the object vo
	 * @param ds the ds
	 * @return the object
	 * @throws Throwable the throwable
	 */
	public Object invoke(JaioObjectVO objectVO, SimpleDataSource ds)
			throws Throwable {

		Method method = objectVO.getMethod();
		Object[] args = objectVO.getArgs();
		Class clazz = objectVO.getClazz();
		Object bean = objectVO.getBean();

		Object retValue = null;

		try {

			//establecer nodo para ejecutar
			GridNodo nodo = seleccionarNodo();
			
			if(nodo==null || nodo.getIp()==null){//si no encontro nodo activo para la operacion
				retValue = ejecutarLocal(objectVO, ds);
			}else{
				//ejecutar accion
				String resultado = ejecutarAccion(objectVO, nodo);
				
				//establecer resultado
				retValue = establecerResultado(objectVO, ds, nodo, resultado);
			}

		} catch (Throwable e) {
			if (e instanceof InvocationTargetException) {
				e = ((InvocationTargetException) e).getTargetException();
			}
			// e.printStackTrace();

			// in case of error the connection must be rolled back...
			if (ds != null && ds.getCurrentConn() != null) {
				ds.getCurrentConn().rollback();
				ds.releaseConnection(ds.getCurrentConn());
			}
			// throw e;
			Response res = null;
			if (Response.class.isAssignableFrom(method.getReturnType())) {
				res = (Response) method.getReturnType().newInstance();
				method.getReturnType()
						.getMethod("setErrorMessage",
								new Class[] { String.class })
						.invoke(res, new Object[] { e.getMessage() });
				method.getReturnType()
						.getMethod("setError", new Class[] { Boolean.TYPE })
						.invoke(res, new Object[] { Boolean.TRUE });
				return res;
			} else {
				throw e;
			}
		}

		return retValue;

	}

	/**
	 * 
	 * Establecer el resultado de la ejecucion de la peticion en el nodo
	 * 
	 * @param objectVO peticion encapsulada
	 * @param ds data source de la conexion
	 * @param nodo instancia del nodo donde se realizo la operacion
	 * @param resultado resultado obttenido de la ejecucion
	 * @return retorno de la peticion
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws SQLExceptionestablecerResultado
	 */
	private Object establecerResultado(JaioObjectVO objectVO,
			SimpleDataSource ds,GridNodo nodo, String resultado) throws IllegalAccessException,
			InvocationTargetException, SQLException {
		
		
		Object retValue;
		resultado = extraerResultadoNodo(nodo, resultado);
		
		if (resultado.isEmpty()) {
			
			 retValue = ejecutarLocal(objectVO, ds);
			
			//actualizar el estado del nodo a inactivo
			try {
				nodo.getEstado().setIdEstado(0);
				nodo = trabajosServicioImpl.guardarNodos(nodo);
			} catch (Exception e) {
				// TODO: handle exception
			}
			
		} else {
			int inicioXmlIndex = resultado.indexOf("InicioXml")
					+ "InicioXml".length();
			int finXmlIndex = resultado.indexOf("FinXml");
			String resultadoXml = "";
			resultadoXml = resultado.substring(inicioXmlIndex, finXmlIndex);
			objectVO = (JaioObjectVO) XmlUtil.fromXml(resultadoXml,
					objectVO);
			retValue = objectVO.getRetorno();
			LOG.info("*******RESULTADO XML*********" + resultadoXml);
		}
		return retValue;
	}

	/**
	 * @param objectVO
	 * @param ds
	 * @return
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws SQLException
	 */
	public Object ejecutarLocal(JaioObjectVO objectVO, SimpleDataSource ds)
			throws IllegalAccessException, InvocationTargetException,
			SQLException {
		Object retValue;
		Method method=objectVO.getMethod();
		 Object[] args=objectVO.getArgs();
		 Object bean=objectVO.getBean();
		// realizar la peticion en el serivdor local
		retValue = method.invoke(bean, args);
		if (!method.getName().equals("setDataSource")
				&& !method.getName().equals("setConn")
				&& !method.getName().equals("getConn")) {

			if (ds != null && ds.getCurrentConn() != null) {
				ds.getCurrentConn().commit();
				ds.releaseConnection(ds.getCurrentConn());
			}
			objectVO.setRetorno(retValue);
		}
		return retValue;
	}

	/**
	 * Ejecutar la accion en el nodo especificado
	 * @param objectVO peticion encapsulada
	 * @param nodo nodo de la grilla para ejecutar la peticion
	 * @return Resultado de la operacion en xml
	 * @throws Exception
	 */
	private String ejecutarAccion(JaioObjectVO objectVO, GridNodo nodo)
			throws Exception {
		//establecer comando
		String xml = XmlUtil.toXml(objectVO);
		LOG.info("**********BEFORE**********" + xml);
		String comando = "/usr/lib/jvm/java-6-sun/bin/java -jar " +
		// "/datos/Documents/Ricardo/Universidad/ProyectoIng/workspace/OPEN_ERP_GRID/jar/ejecutor/target/ejecutor-0.0.1-SNAPSHOT.jar "
		// +
		nodo.getJar() + " " + "\"" + xml + "\"";
		
		//ejecutar comando
		String resultado = Consola.ejecutarComando(comando, nodo.getIp());
		return resultado;
	}


	/**
	 * Consulta para establecer nodo para ejecutar la peticion en la grilla
	 * @return instancia del nodo para ejecutar la accion
	 * @throws FinderException
	 * @throws CrudException
	 */
	private GridNodo seleccionarNodo() throws FinderException, CrudException {
		GridNodo nodo = null;
		// consultar el indice de ejecucion
		int indice = trabajosServicioImpl.consultarIndice();
		nodo = new GridNodo();
		nodo.setIndice(indice);
		// establecer nodo
		Estado estado = new Estado();
		estado.setIdEstado(1);
		nodo.setEstado(estado);
		nodo = trabajosServicioImpl.consultarNodo(nodo);
		return nodo;
	}

	/**
	 * Extraer resultado de la respuesta en xml.
	 *
	 * @param nodo el nodo
	 * @param resultado el resultado obtenido de la ejecucion
	 * @return the string
	 */
	private String extraerResultadoNodo(GridNodo nodo, String resultado) {
		int inicioXmlInde = resultado.indexOf("InicioXml")
				+ "InicioXml".length();
		int finXmlInde = resultado.indexOf("FinXml");
		if (inicioXmlInde == -1 || finXmlInde == -1) {

			String prefixUid = "Job ID: uuid:";
			int uidLenght = 36;

			int jobIdIndex = resultado.indexOf(prefixUid);
			String jobId = "";

			if (jobIdIndex == -1) {
				resultado = "";
			} else {
				int iniJob = jobIdIndex + prefixUid.length();
				int finJob = iniJob + uidLenght;
				jobId = resultado.substring(iniJob, finJob);

				FtpUtil ftp = new FtpUtil(nodo.getIp().substring(0,
						nodo.getIp().lastIndexOf(":")));
				byte archivo[] = ftp.obtenerArchivoFtp(jobId + ".0.stdout");
				resultado = new String(archivo);
			}

		}
		return resultado;
	}
}
